package cn.topcodes.tcsf.app.service.impl.report;

import cn.topcode.unicorn.utils.CollectionUtil;
import cn.topcode.unicorn.utils.DateUtil;
import cn.topcode.unicorn.utils.StringUtil;
import cn.topcodes.tcsf.app.domain.entity.AppClient;
import cn.topcodes.tcsf.app.domain.entity.AppUser;
import cn.topcodes.tcsf.app.domain.entity.DailyActiveUsers;
import cn.topcodes.tcsf.app.domain.report.ContinuousVo;
import cn.topcodes.tcsf.app.domain.report.PieData;
import cn.topcodes.tcsf.app.domain.report.PieVo;
import cn.topcodes.tcsf.app.mapper.AppClientMapper;
import cn.topcodes.tcsf.app.service.AppClientService;
import cn.topcodes.tcsf.app.service.AppUserService;
import cn.topcodes.tcsf.app.service.DailyActiveUsersService;
import cn.topcodes.tcsf.app.service.report.ReportService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by yvan
 * Author: Yvan
 * Date: 2017/12/12
 * Time: 下午4:26
 **/
@Service("appReportService")
public class ReportServiceImpl implements ReportService {

    @Autowired
    private AppUserService appUserService;

    @Autowired
    private AppClientService appClientService;

    @Autowired
    private AppClientMapper appClientMapper;

    @Autowired
    private DailyActiveUsersService dailyActiveUsersService;

    public static Map<String, Double> USERRETENTION = null;

    /**
     * 新账号趋势图
     *
     * @param sStartTime 开始时间
     * @param sEndTime   结束时间
     * @return
     */
    @Override
    public ContinuousVo getNewAccounts(String sStartTime, String sEndTime) {
        ContinuousVo vo = new ContinuousVo();
        Date time = null;
        Date end = null;
        Example example = appUserService.buildExample();
        if (StringUtil.isNotBlank(sStartTime) && StringUtil.isNotBlank(sEndTime)) {
            try {
                time = DateUtil.change0H0M0S(DateUtil.parse(sStartTime, DateUtil.YYYY_MM_DD));
                end = DateUtil.change23H59M59S(DateUtil.parse(sEndTime, DateUtil.YYYY_MM_DD));
            } catch (ParseException e) {
                e.printStackTrace();
            }
        } else {
            time = DateUtil.change0H0M0S(setMonth(new Date(), -1));
            end = new Date();
        }
        vo.setTitle(DateUtil.format(time, "dd/MM") + "-" + DateUtil.format(end, "dd/MM" + " New user registration trend chart"));
        example.or().andGreaterThan("createdTime", time)
                .andLessThan("createdTime", end);
        List<AppUser> appUsers = appUserService.findAll(example);
        List<String> dates = new ArrayList<>();
        List<Long> count = new ArrayList<>();
        while (time.getTime() <= end.getTime()) {
            Date startTime = time;
            Date endTime = setDay(time, 1);
            dates.add(DateUtil.format(time, DateUtil.YYYY_MM_DD));
            if (null != appUsers && !appUsers.isEmpty()) {
                count.add(appUsers.stream()
                        .filter((e) -> e.getCreatedTime().getTime() >= startTime.getTime() && e.getCreatedTime().getTime() <= endTime.getTime())
                        .count());
            } else {
                count.add(0L);
            }
            time = endTime;
        }
        vo.setDates(dates);
        vo.setCounts(count);
        return vo;
    }

    /**
     * 在线用户趋势图
     *
     * @param sStartTime 开始时间
     * @param sEndTime   结束时间
     * @param type       APP类型
     * @return
     */
    @Override
    public PieVo getOnlineAccounts(String sStartTime, String sEndTime, String type) {
        PieVo pieVo = new PieVo();
        Date time = null;
        Date end = null;
        Example example = appClientService.buildExample();
        if (StringUtil.isNotBlank(sStartTime) && StringUtil.isNotBlank(sEndTime)) {
            try {
                time = DateUtil.change0H0M0S(DateUtil.parse(sStartTime, DateUtil.YYYY_MM_DD));
                end = DateUtil.change23H59M59S(DateUtil.parse(sEndTime, DateUtil.YYYY_MM_DD));
            } catch (ParseException e) {
                e.printStackTrace();
            }
        } else {
            time = DateUtil.change0H0M0S(setMonth(new Date(), -1));
            end = new Date();
        }
        if (StringUtil.isNotBlank(type)) {
            example.or().andGreaterThanOrEqualTo(AppClient.FIELD_LAST_ACTIVE_TIME, time)
                    .andLessThanOrEqualTo(AppClient.FIELD_LAST_ACTIVE_TIME, end)
                    .andEqualTo(AppClient.FIELD_APP, type);
        } else {
            example.or().andGreaterThanOrEqualTo(AppClient.FIELD_LAST_ACTIVE_TIME, time)
                    .andLessThanOrEqualTo(AppClient.FIELD_LAST_ACTIVE_TIME, end);
        }

        int appUsersCount = appUserService.countAll();
        List<AppClient> appClients = appClientService.findAll(example);
        String appType = "";
        if (StringUtil.isNotBlank(type)) {
            if (AppClient.APP_DEFAULT.equals(type)) {
                appType = "Default application";
            } else if ("Baby".equals(type)) {
                appType = "Shike XiaoBao";
            } else if ("XiaoWeiShi".equals(type)) {
                appType = "Shike XiaoWeiShi";
            }
        }
        pieVo.setTitle(DateUtil.format(time, "dd/MM") + "-" + DateUtil.format(end, "dd/MM" + appType + " User active statistics"));
        List<PieData> datas = new ArrayList<>();
        List<String> legendData = Arrays.asList("Active Users", "Zombie users");
        PieData onLine = new PieData();
        onLine.setName("Active Users");
        long activeAppUsers = 0;
        if (null != appClients && !appClients.isEmpty()) {
            activeAppUsers = appClients.stream()
                    .map(AppClient::getAppUserId)
                    .distinct()
                    .count();
            onLine.setValue(activeAppUsers + "");
        } else {
            onLine.setValue(0 + "");
        }
        onLine.setActive(true);
        datas.add(onLine);

        PieData offLine = new PieData();
        offLine.setName("Zombie users");
        offLine.setValue(appUsersCount - activeAppUsers + "");
        offLine.setActive(false);
        datas.add(offLine);
        pieVo.setLegendData(legendData);
        pieVo.setAppData(datas);
        return pieVo;
    }

    /**
     * 获取用户的日 周 月活跃量
     *
     * @param sStartTime 开始时间
     * @param type       时间类型 日 周 月
     */
    @Override
    public ContinuousVo getUserActiveCount(String sStartTime, String sEndTime, String type) {

        ContinuousVo vo = new ContinuousVo();
        Date time = null;
        Date end = null;
        Example example = dailyActiveUsersService.buildExample();
        switch (type) {
            case "day":
                try {
                    time = DateUtil.parse(sStartTime, DateUtil.YYYY_MM_DD);
                    end = DateUtil.parse(sEndTime, DateUtil.YYYY_MM_DD);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                vo.setTitle(DateUtil.format(time, "dd/MM/yyyy") + "-" + DateUtil.format(end, "dd/MM/yyyy" + " User active trend chart"));
                break;
            case "month":
                //按年统计的话 +1月
                try {
                    time = DateUtil.parse(sStartTime, "yyyy-MM");
                    end = DateUtil.change23H59M59S(getMonthLastDay(DateUtil.parse(sEndTime, "yyyy-MM")));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                vo.setTitle(DateUtil.format(time, "MM/yyyy") + "-" + DateUtil.format(end, "MM/yyyy" + " User active trend chart"));
                break;
            case "year":
                //按年统计的话 +1年
                try {
                    time = DateUtil.parse(sStartTime, "yyyy");
                    end = DateUtil.change23H59M59S(getYearLastDay(DateUtil.parse(sEndTime, "yyyy")));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                vo.setTitle(DateUtil.format(time, "yyyy") + "-" + DateUtil.format(end, "yyyy" + " User active trend chart"));
                break;
            default:
                break;
        }
        example.or().andGreaterThanOrEqualTo(DailyActiveUsers.FIELD_DAU_DATE, time)
                .andLessThanOrEqualTo(DailyActiveUsers.FIELD_DAU_DATE, end);
        List<DailyActiveUsers> dailyActiveUsers = dailyActiveUsersService.findAll(example);
        List<String> dates = new ArrayList<>();
        List<Long> count = new ArrayList<>();
        while (time.getTime() <= end.getTime()) {
            Date startTime = time;
            Date tempTime = null;
            switch (type) {
                case "day":
                    tempTime = setDay(time, 1);
                    dates.add(DateUtil.format(time, "dd/MM/yyyy"));
                    break;
                case "month":
                    tempTime = setMonth(time, 1);
                    dates.add(DateUtil.format(time, "MM/yyyy"));
                    break;
                case "year":
                    tempTime = setYear(time, 1);
                    dates.add(DateUtil.format(time, "yyyy"));
                    break;
                default:
                    break;
            }
            Date endTime = tempTime;
            if (null != dailyActiveUsers && !dailyActiveUsers.isEmpty()) {
                count.add(dailyActiveUsers.stream()
                        .filter((e) -> e.getDauDate().getTime() >= startTime.getTime() && e.getDauDate().getTime() < endTime.getTime())
                        .count());
            } else {
                count.add(0L);
            }
            time = tempTime;
        }
        vo.setDates(dates);
        vo.setCounts(count);
        return vo;
    }

    @Override
    public List<PieVo> getAppInfo(String type) {
        List<PieVo> pieVos = new ArrayList<>();

        PieVo appPltform = new PieVo();
        Example example = appClientService.buildExample();
        if (StringUtil.isNotBlank(type)) {
            example.or().andEqualTo(AppClient.FIELD_APP, type);
        }
        //获取所有的APPClient信息
        List<AppClient> appClients = appClientService.findAll(example);
        appClients.stream()
                .map(e -> e.getAppUserId())
                .distinct()
                .collect(Collectors.toList());
        appPltform.setTitle("App platform information");
        List<String> allAppPlatform = appClientMapper.findAllAppPlatform();
        List<PieData> appPltformCount = new ArrayList<>();

        for (String platform : allAppPlatform) {
            PieData pieData = new PieData();
            pieData.setValue(appClients.stream()
                    .filter(e -> e.getPlatform().equals(platform))
                    .count() + "");
            if (AppClient.APP_VERSION_UNKNOWN.equals(platform)) {
                pieData.setName("Platform" + platform);
            } else {
                pieData.setName(platform);
            }
            appPltformCount.add(pieData);
        }
        allAppPlatform.stream()
                .filter(e -> e.equals(AppClient.APP_VERSION_UNKNOWN))
                .collect(Collectors.toList());
        allAppPlatform.add("Platform" + AppClient.APP_VERSION_UNKNOWN);
        appPltform.setLegendData(allAppPlatform);
        appPltform.setAppData(appPltformCount);

        PieVo appVersion = new PieVo();
        appClients.stream()
                .map(e -> e.getAppUserId())
                .distinct()
                .collect(Collectors.toList());
        appVersion.setTitle("App platform information");
        List<String> allAppVersion = appClientMapper.findAllAppVersion();
        List<PieData> appVersionCount = new ArrayList<>();

        for (String appVerison : allAppVersion) {
            PieData pieData = new PieData();
            if (AppClient.APP_VERSION_UNKNOWN.equals(appVerison)) {
                pieData.setName("Version" + appVerison);
            } else {
                pieData.setName(appVerison);
            }
            pieData.setValue(appClients.stream()
                    .filter(e -> e.getAppVersion().equals(appVerison))
                    .count() + "");
            appVersionCount.add(pieData);
        }
        allAppVersion.stream()
                .filter(e -> e.equals(AppClient.APP_VERSION_UNKNOWN))
                .collect(Collectors.toList());
        allAppVersion.add("Version" + AppClient.APP_VERSION_UNKNOWN);
        appVersion.setAppData(appVersionCount);
        appVersion.setLegendData(allAppVersion);

        pieVos.add(appPltform);
        pieVos.add(appVersion);
        return pieVos;
    }

    /**
     * 获取用户的留存率
     *
     * @param sStartTime 开始时间
     * @param dateType   时间类型 日 周 月
     */
    @Override
    public ContinuousVo getUserRetention(String sStartTime, String sEndTime, String dateType) {

        ContinuousVo vo = new ContinuousVo();
        Date time = null;
        Date end = null;
        Example example = appUserService.buildExample();
        if (StringUtil.isNotBlank(sStartTime) && StringUtil.isNotBlank(sEndTime)) {
            try {
                time = DateUtil.change0H0M0S(DateUtil.parse(sStartTime, DateUtil.YYYY_MM_DD));
                end = DateUtil.change23H59M59S(DateUtil.parse(sEndTime, DateUtil.YYYY_MM_DD));
            } catch (ParseException e) {
                e.printStackTrace();
            }
        } else {
            time = DateUtil.change0H0M0S(setWeek(new Date(), -1));
            end = DateUtil.change23H59M59S(setWeek(new Date(), 1));
        }
        example.or().andGreaterThan("createdTime", time)
                .andLessThan("createdTime", end);
        List<AppUser> appUsers = appUserService.findAll(example);
        vo.setTitle(DateUtil.format(time, "dd/MM/yyyy") + "-" + DateUtil.format(end, "dd/MM/yyyy" + "User retention trend graph"));
        //得到这段时间内共注册了多少用户，然后算留存率
        List<String> dates = Arrays.asList("Day 2", "Day 3", "Day 7", "Day 10", "Day 15", "Day 20", "Day 30");
        List<Long> count = new ArrayList<>();

        List<Double> days1 = new ArrayList<>();
        List<Double> days3 = new ArrayList<>();
        List<Double> days7 = new ArrayList<>();
        List<Double> days10 = new ArrayList<>();
        List<Double> days15 = new ArrayList<>();
        List<Double> days20 = new ArrayList<>();
        List<Double> days30 = new ArrayList<>();

        Map<String, Long> userCountMap = new HashMap<>();
        if (null != appUsers && !appUsers.isEmpty()) {
            while (time.getTime() <= end.getTime()) {
                Date startTime = time;
                Date endTime = setDay(time, 1);
                //当天注册的用户
                List<AppUser> appUserList = appUsers.stream()
                        .filter((e) -> e.getCreatedTime().getTime() >= DateUtil.change0H0M0S(startTime).getTime() && e.getCreatedTime().getTime() <= DateUtil.change23H59M59S(startTime).getTime())
                        .collect(Collectors.toList());
                //次日使用的用户数
                Long activeUsers1 = getActiveUsers(appUserList, setDay(startTime, 1));
                Long activeUsers3 = getActiveUsers(appUserList, setDay(startTime, 3));
                Long activeUsers7 = getActiveUsers(appUserList, setDay(startTime, 7));
                Long activeUsers10 = getActiveUsers(appUserList, setDay(startTime, 10));
                Long activeUsers15 = getActiveUsers(appUserList, setDay(startTime, 15));
                Long activeUsers20 = getActiveUsers(appUserList, setDay(startTime, 20));
                Long activeUsers30 = getActiveUsers(appUserList, setDay(startTime, 30));
                long userCount = (appUserList.isEmpty() ? 0L : appUserList.size());
                days1.add(userCount == 0 ? 0 : (double) activeUsers1 / userCount);
                days3.add(userCount == 0 ? 0 : (double) activeUsers3 / userCount);
                days7.add(userCount == 0 ? 0 : (double) activeUsers7 / userCount);
                days10.add(userCount == 0 ? 0 : (double) activeUsers10 / userCount);
                days15.add(userCount == 0 ? 0 : (double) activeUsers15 / userCount);
                days20.add(userCount == 0 ? 0 : (double) activeUsers20 / userCount);
                days30.add(userCount == 0 ? 0 : (double) activeUsers30 / userCount);
                userCountMap.put("day1Count", userCountMap.get("day1Count") == null ? activeUsers1 : userCountMap.get("day1Count") + activeUsers1);
                userCountMap.put("day3Count", userCountMap.get("day3Count") == null ? activeUsers3 : userCountMap.get("day3Count") + activeUsers3);
                userCountMap.put("day7Count", userCountMap.get("day7Count") == null ? activeUsers7 : userCountMap.get("day7Count") + activeUsers7);
                userCountMap.put("day10Count", userCountMap.get("day10Count") == null ? activeUsers10 : userCountMap.get("day10Count") + activeUsers10);
                userCountMap.put("day15Count", userCountMap.get("day15Count") == null ? activeUsers15 : userCountMap.get("day15Count") + activeUsers15);
                userCountMap.put("day20Count", userCountMap.get("day20Count") == null ? activeUsers20 : userCountMap.get("day20Count") + activeUsers20);
                userCountMap.put("day30Count", userCountMap.get("day30Count") == null ? activeUsers30 : userCountMap.get("day30Count") + activeUsers30);
                time = endTime;
            }
        }
        USERRETENTION = new HashMap<>();

        double day1Avg = new BigDecimal(days1.stream().collect(Collectors.averagingDouble(d -> d))).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        USERRETENTION.put("days1", day1Avg);

        double day3Avg = new BigDecimal(days3.stream().collect(Collectors.averagingDouble(d -> d))).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        USERRETENTION.put("days3", day3Avg);

        double day7Avg = new BigDecimal(days7.stream().collect(Collectors.averagingDouble(d -> d))).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        USERRETENTION.put("days7", day7Avg);

        double day10Avg = new BigDecimal(days10.stream().collect(Collectors.averagingDouble(d -> d))).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        USERRETENTION.put("days10", day10Avg);

        double day15Avg = new BigDecimal(days15.stream().collect(Collectors.averagingDouble(d -> d))).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        USERRETENTION.put("days15", day15Avg);

        double day20Avg = new BigDecimal(days20.stream().collect(Collectors.averagingDouble(d -> d))).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        USERRETENTION.put("days20", day20Avg);

        double day30Avg = new BigDecimal(days30.stream().collect(Collectors.averagingDouble(d -> d))).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        USERRETENTION.put("days30", day30Avg);

        count.add(userCountMap.get("day1Count") == null ? 0L : userCountMap.get("day1Count"));
        count.add(userCountMap.get("day3Count") == null ? 0L : userCountMap.get("day3Count"));
        count.add(userCountMap.get("day7Count") == null ? 0L : userCountMap.get("day7Count"));
        count.add(userCountMap.get("day10Count") == null ? 0L : userCountMap.get("day10Count"));
        count.add(userCountMap.get("day15Count") == null ? 0L : userCountMap.get("day15Count"));
        count.add(userCountMap.get("day20Count") == null ? 0L : userCountMap.get("day20Count"));
        count.add(userCountMap.get("day30Count") == null ? 0L : userCountMap.get("day30Count"));
        vo.setDates(dates);
        vo.setCounts(count);
        return vo;
    }

    public Long getActiveUsers(List<AppUser> appUsers, Date startTime) {
        List<Long> userIds = CollectionUtil.getPropList(appUsers, "id");
        if (null != userIds && !userIds.isEmpty()) {
            Example example = dailyActiveUsersService.buildExample();
            example.or()
                    .andEqualTo(DailyActiveUsers.FIELD_DAU_DATE, DateUtil.format(startTime, DateUtil.YYYY_MM_DD))
                    .andIn(DailyActiveUsers.FIELD_APP_USER_ID, userIds);
            List<DailyActiveUsers> dailyActiveUsers = dailyActiveUsersService.findAll(example);
            long count = dailyActiveUsers.stream()
                    .filter((e) -> e.getDauDate().getTime() == DateUtil.change0H0M0S(startTime).getTime() && userIds.contains(e.getAppUserId()))
                    .count();
            return count;
        }
        return 0L;
    }

    public int getUserRetentionCount(Date time, int days, List<Long> userIds) {
        Example activeExample = dailyActiveUsersService.buildExample();
        activeExample.or().andEqualTo(DailyActiveUsers.FIELD_DAU_DATE, setDay(time, days))
                .andIn(DailyActiveUsers.FIELD_APP_USER_ID, userIds);
        List<DailyActiveUsers> activeUsers = dailyActiveUsersService.findAll(activeExample);
        return activeUsers.size();
    }

    public int getDay(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.DAY_OF_MONTH);
    }

    public int getMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.MONTH);
    }

    /**
     * 设置周数 正数增加 负数减少 单位为周
     *
     * @param time
     * @param week
     * @return
     */
    public Date setWeek(Date time, int week) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(time);
        calendar.add(Calendar.DATE, (week * 7) - 1);
        return calendar.getTime();
    }

    /**
     * 设置天数 正数增加 负数减少 单位为天
     *
     * @param time
     * @param days
     * @return
     */
    public Date setDay(Date time, int days) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(time);
        calendar.add(Calendar.DATE, days);// 今天 +1
        return calendar.getTime();
    }

    /**
     * 设置月份 正数增加 负数减少 单位为月
     *
     * @param date
     * @param month
     * @return
     */
    public Date setMonth(Date date, int month) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MONTH, month);
        return calendar.getTime();
    }

    /**
     * 设置年份 正数增加 负数减少 单位为年
     *
     * @param date
     * @param year
     * @return
     */
    public Date setYear(Date date, int year) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.YEAR, year);
        return calendar.getTime();
    }

    public Date getMonthLastDay(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MONTH, 1);
        calendar.set(Calendar.DAY_OF_MONTH, 0);
        return calendar.getTime();
    }

    public Date getYearLastDay(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);

        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.set(Calendar.YEAR, cal.get(Calendar.YEAR));
        calendar.roll(Calendar.DAY_OF_YEAR, -1);
        return calendar.getTime();
    }

}
